package nl.vu.few.ibivu.biomodels.client.activity;

import static nl.vu.few.ibivu.biomodels.client.event.MessageBroadcastType.*;

import nl.vu.few.ibivu.biomodels.client.ModelServiceAsync;
import nl.vu.few.ibivu.biomodels.client.event.ModelChangedEvent;
import nl.vu.few.ibivu.biomodels.client.event.ModelDrawnEvent;
import nl.vu.few.ibivu.biomodels.client.event.RequestModelOperationEvent;
import nl.vu.few.ibivu.biomodels.client.event.RequestModelOperationEventHandler;
import nl.vu.few.ibivu.biomodels.client.place.ModelPlace;
import nl.vu.few.ibivu.biomodels.client.ui.view.ModelView;
import nl.vu.few.ibivu.biomodels.shared.Model;
import nl.vu.few.ibivu.biomodels.shared.PetrifierModelData;
import nl.vu.few.ibivu.biomodels.shared.UserProfile;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.place.shared.PlaceController;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;

public class ModelActivity extends PresenterActivity implements
		ModelView.Presenter {

	private ModelView view;
	private ModelActivityState state;
	private ModelServiceAsync ms;
	private HandlerRegistration registration;
	private boolean checkExecution;

	// original model (with id if available)
	private Model model;
	// temporary model (always without id)
	private Model ledom;
	
	
	private Long modelId;

	@Inject
	public ModelActivity(EventBus eventBus, PlaceController placeController,
			final ModelServiceAsync ms, final ModelView view) {
		super(eventBus, placeController);
		this.view = view;
		this.ms = ms;
		this.state = ModelActivityState.CLEAN;
		this.registration = RequestModelOperationEvent.register(eventBus,
				new RequestModelOperationEventHandler() {

					@Override
					public void onOperationRequest(
							RequestModelOperationEvent event) {
						switch (event.getOperation()) {
							case SAVEAS:
								ModelActivity.this.view.showLoader();
								ledom.setData(view.buildModelData());
								ledom.setThumbnail(view.buildThumbnail());
								// save the temporary model as a new model
								ms.save(ledom, new SaveAsyncCallback());
								break;
							case SAVE:
								// save only if the state is DIRTY
								if (state == ModelActivityState.CLEAN) {
									broadcastMessage(INFO,
											"Model " + ledom.getName() + " doesn't need to be saved.");
								}
								else {
									view.showLoader();
									ledom.setData(view.buildModelData());
									ledom.setThumbnail(view.buildThumbnail());
									// update the values of the original model based on the new values
									// of the temporary model
									model.copyData(ledom);
									// save the original (and updated) model
									ms.save(model, new SaveAsyncCallback());
								}
								break;
							case EXECUTE:
								if (state != ModelActivityState.CLEAN) {
									broadcastMessage(ERROR, "Model " + model.getName() + " needs to be saved first.");
								}
								else {
									ms.execute(model.getId(), new ExecuteAsyncCallback());
								}
								break;
						}
					}
				});
	}

	public ModelActivity withPlace(ModelPlace place) {
		modelId = place.getModelId();
		return this;
	}

	private void setModel(Model model) {
		this.model = model;
		ledom = new Model().copyData(model);
		setClean();
	}

	@Override
	public void start(AcceptsOneWidget panel, EventBus eventBus) {
		if (modelId == null) {
			// Create new model
			setModel(new Model());
			view.setPresenter(this);
			view.drawModel();
		} else {
			view.showLoader();
			// Load model from datastore
			ms.fetch(modelId, new AsyncCallback<Model>() {

				@Override
				public void onFailure(Throwable caught) {
					String details = caught.getMessage();
					broadcastMessage(ERROR, details);
					return;
				}

				@Override
				public void onSuccess(Model result) {
					setModel(result);
					view.setPresenter(ModelActivity.this);
					view.drawModel();
				}
			});
		}
		panel.setWidget(view);
	}

	@Override
	public String mayStop() {
		if (state == ModelActivityState.DIRTY) {
			return "Do you want to discard your changes to the model?";
		}
		return null;
	}

	@Override
	public void onCancel() {
		// Avoid that the current presenter receives messages while waiting to
		// be disposed
		if (registration != null)
			registration.removeHandler();
		super.onCancel();
	}

	@Override
	public void onStop() {
		// Avoid that the current presenter receives messages while waiting to
		// be disposed
		if (registration != null)
			registration.removeHandler();
		super.onStop();
	}

	@Override
	public Widget getView() {
		return this.view.asWidget();
	}

	private void setDirty() {
		if (state == ModelActivityState.DIRTY)
			return;
		state = ModelActivityState.DIRTY;
		eventBus.fireEvent(new ModelChangedEvent(state));
	}

	private void setClean() {
		if (state == ModelActivityState.CLEAN)
			return;
		state = ModelActivityState.CLEAN;
		eventBus.fireEvent(new ModelChangedEvent(state));
	}

	@Override
	public void setDrawn() {
		view.clearLoader();
		eventBus.fireEvent(new ModelDrawnEvent());
	}

	@Override
	public PetrifierModelData getModelData() {
		return ledom.getData();
	}

	@Override
	public void invalidateModelData() {
		setDirty();
	}

	@Override
	public void setName(String name) {
		ledom.setName(name);
		setDirty();
	}

	@Override
	public void setDescription(String description) {
		ledom.setDescription(description);
		setDirty();
	}

	@Override
	public void setPublic(boolean value) {
		ledom.setPublic(value);
		setDirty();
	}

	@Override
	public String getName() {
		return ledom.getName();
	}

	@Override
	public String getDescription() {
		return ledom.getDescription();
	}

	@Override
	public boolean isPublic() {
		return ledom.isPublic();
	}

	@Override
	public UserProfile getOwner() {
		return ledom.getOwner();
	}

	private class SaveAsyncCallback implements AsyncCallback<Model> {

		@Override
		public void onFailure(Throwable caught) {
			// TODO check if was a login
			// problem and in case request a
			// login check.
			view.clearLoader();
			broadcastMessage(ERROR, caught.getMessage());
		}

		@Override
		public void onSuccess(Model result) {
			setModel(result);
			setClean();
			view.clearLoader();
			broadcastMessage(INFO, "Model " + ledom.getName()+ " has been saved.");
		}
	}
	
	private class ExecuteAsyncCallback implements  AsyncCallback<Void> {
		@Override
		public void onFailure(Throwable caught) {
			broadcastMessage(ERROR, "Model " + model.getName() + " cannot be executed.");												
		}

		@Override
		public void onSuccess(Void result) {
			broadcastMessage(INFO, "Model "	+ model.getName() + " has been successfully queued for execution.");
			checkExecution = true;
			Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
				@Override
				public boolean execute() {
					ms.isExecuted(model.getId(), new CheckExecutionAsyncCallback());
					return checkExecution;
				}
			}, 10000);
		}
	}
	
	private class CheckExecutionAsyncCallback implements AsyncCallback<Boolean> {

		@Override
		public void onFailure(Throwable caught) {
			broadcastMessage(ERROR, "Model " + model.getName() + " has NOT been successfully executed!");
			checkExecution = false;
		}

		@Override
		public void onSuccess(Boolean result) {
			if (result && checkExecution) {
				broadcastMessage(INFO, "Model "	+ model.getName() + " has been successfully executed!");
				checkExecution = false;
			}
		}
		
	}
}
